// PanelItemOpen.cpp

#include "StdAfx.h"

#include "Directory.h"

#include "Common/String/StringConvert.h"

#include "Common/System/Error.h"
#include "Common/FileSystem/FileDir.h"
#include "Common/FileSystem/FileFind.h"
#include "Common/System/Process.h"
#include "Common/MS/PropVariant.h"

#include "Common/FileSystem/ExtractingFilePath.h"

#include "../Windows.h"

#include "FileFolderPluginOpen.h"
#include "Common/String/FormatUtils.h"
#include "Common/Lang/LangUtils.h"
#include "Common/Registry/RegistryUtils.h"
#include "UpdateCallback100.h"	

#include "../../resource.h"
#include "../../PanelMessages.h"

#include "Common/System/Threading/Thread.h"
#include <memory>

namespace FileManager
{
namespace FileSystem
{


using namespace Common;
using namespace System;

//static LPTSTR kTempDirPrefix = _T("7zO");


static bool IsNameVirus(const CString &name)
{
	return (name.Find(_T("     ")) >= 0);
}

struct CTmpProcessInfo: public CTempFileInfo
{
	HANDLE ProcessHandle;
	HWND Window;
	CString FullPathFolderPrefix;
	bool UsePassword;
	CString Password;
	CTmpProcessInfo(): UsePassword(false) {}
};

class CTmpProcessInfoRelease
{
	CTmpProcessInfo* _tmpProcessInfo;
public:
	bool _needDelete;
	CTmpProcessInfoRelease(CTmpProcessInfo &tmpProcessInfo):
		_tmpProcessInfo(&tmpProcessInfo), _needDelete(true) {}
	~CTmpProcessInfoRelease()
	{
		if (_needDelete)
		{
			_tmpProcessInfo->DeleteDirAndFile();
		}
	}
};

HRESULT CDirectory::OpenItemAsArchive(IInStream* inStream,
								  const CTempFileInfo &tempFileInfo,
								  const CString &virtualFilePath,
								  const CString &arcFormat,
								  bool &encrypted)
{
	encrypted = false;
	std::shared_ptr<CFolderLink> folderLink = std::make_shared<CFolderLink>();
	(CTempFileInfo &)*(folderLink.get()) = tempFileInfo;
	if (inStream)
	{
		folderLink->IsVirtual = true;
	}
	else
	{
		if (!folderLink->FileInfo.Find(folderLink->FilePath))
		{
			return ::GetLastError();
		}
		if (folderLink->FileInfo.IsDir())
		{
			return S_FALSE;
		}
		folderLink->IsVirtual = false;
	}

	folderLink->VirtualPath = virtualFilePath;

	CComPtr<IFolderFolder> newFolder;

	// _passwordIsDefined = false;
	// _password.Empty();

	DLL::CLibrary library;

	CString password;
	HRESULT hRes(OpenFileFolderPlugin(inStream,
									  folderLink->FilePath.IsEmpty() ? virtualFilePath : folderLink->FilePath,
									  arcFormat,
									  &library, &newFolder, m_hWnd, encrypted, password, &m_pool));
	if(S_OK != hRes)
	{
		return hRes;
	}

	folderLink->Password = password;
	folderLink->UsePassword = encrypted;

	folderLink->ParentFolder = _folder;
	_parentFolders.push_back(folderLink);
	folderLink->Library.Attach(_library.Detach());

	_folder.Release();
	_library.Free();
	_folder = newFolder;
	_library.Attach(library.Detach());

	_flatMode = _flatModeForArc;

	CComPtr<IGetFolderArcProps> getFolderArcProps;
	_folder.QueryInterface(&getFolderArcProps);
	if (getFolderArcProps)
	{
		CComPtr<IFolderArcProps> arcProps;
		getFolderArcProps->GetFolderArcProps(&arcProps);
		if (arcProps)
		{
			CString s;
			UINT32 numLevels;
			if (arcProps->GetArcNumLevels(&numLevels) != S_OK)
			{
				numLevels = 0;
			}
			for (UINT32 level2 = 0; level2 < numLevels; level2++)
			{
				UINT32 level = numLevels - 1 - level2;
				PROPID propIDs[] = { kpidError, kpidPath, kpidType } ;
				CString values[3];
				for (INT32 i = 0; i < 3; i++)
				{
					CComPropVariant prop;
					if (arcProps->GetArcProp(level, propIDs[i], &prop) != S_OK)
					{
						continue;
					}
					if (prop.vt != VT_EMPTY)
					{
						values[i] = (prop.vt == VT_BSTR) ? prop.bstrVal : _T("?");
					}
				}
				if (!values[0].IsEmpty())
				{
					if (!s.IsEmpty())
					{
						s += _T("--------------------\n");
					}
					s += values[0];
					s += _T("\n\n[");
					s += values[2];
					s += _T("] ");
					s += values[1];
					s += _T("\n");
				}
			}
			if (!s.IsEmpty())
			{
				CWindows::MessageBox(m_hWnd, s);
			}
		}
	}
	//_parentFolders & _folder
	//so update the state
	LoadFullPath();
	return S_OK;
}

HRESULT CDirectory::OpenItemAsArchive(const CString &name, const CString &arcFormat, bool &encrypted)
{
	CTempFileInfo tfi;
	tfi.ItemName = name;
	tfi.FolderPath = GetCurrentDirectory();
	tfi.FilePath = GetCurrentDirectory() + name;
	return OpenItemAsArchive(NULL, tfi, GetCurrentDirectory() + name, arcFormat, encrypted);
}

HRESULT CDirectory::OpenItemAsArchive(int index)
{
	CDisableTimerProcessing disableTimerProcessing1(*this);
	bool encrypted;
	HRESULT hRes(OpenItemAsArchive(GetItemRelPath(index), CString(), encrypted));
	if(S_OK != hRes)
	{
		return hRes;
	}
	return S_OK;
}

HRESULT CDirectory::OpenParentArchiveFolder()
{
	CDisableTimerProcessing disableTimerProcessing1(*this);
	if (_parentFolders.size() < 2)
	{
		return S_OK;
	}
	const auto &folderLink = _parentFolders.back();
	Common::FileSystem::File::CFileInfo newFileInfo;
	if (newFileInfo.Find(folderLink->FilePath))
	{
		if (folderLink->WasChanged(newFileInfo))
		{
			CString message = String::PatternFormat(IDS_WANT_UPDATE_MODIFIED_FILE,
													0x03020280, folderLink->ItemName);
			if (::MessageBox(m_hWnd, message, _T("7-Zip"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
			{
				if (OnOpenItemChanged(folderLink->FolderPath, folderLink->ItemName,
									  folderLink->UsePassword, folderLink->Password) != S_OK)
				{
					::MessageBox(m_hWnd, String::PatternFormat(IDS_CANNOT_UPDATE_FILE,
								 0x03020281, folderLink->FilePath), _T("7-Zip"), MB_OK | MB_ICONSTOP);
					return S_OK;
				}
			}
		}
	}
	folderLink->DeleteDirAndFile();
	return S_OK;
}

static const char* kStartExtensions =
#ifdef UNDER_CE
	" cab"
#endif
	" exe bat com"
	" chm"
	" msi doc xls ppt pps wps wpt wks xlr wdb vsd pub"

	" docx docm dotx dotm xlsx xlsm xltx xltm xlsb xps"
	" xlam pptx pptm potx potm ppam ppsx ppsm xsn"
	" mpp"
	" msg"
	" dwf"

	" flv swf"

	" odt ods"
	" wb3"
	" pdf"
	" ";

static bool FindExt(const char* p, const CString &name)
{
	int extPos = name.ReverseFind('.');
	if (extPos < 0)
	{
		return false;
	}
	CString ext = name.Mid(extPos + 1);
	ext.MakeLower();
	CStringA ext2 = String::UnicodeStringToMultiByte(ext);
	for (int i = 0; p[i] != 0;)
	{
		int j;
		for (j = i; p[j] != ' '; j++);
		if (ext2.GetLength() == j - i && memcmp(p + i, (const char*)ext2, ext2.GetLength()) == 0)
		{
			return true;
		}
		i = j + 1;
	}
	return false;
}

static bool DoItemAlwaysStart(const CString &name)
{
	return FindExt(kStartExtensions, name);
}

CString GetQuotedString(const CString &s)
{
	return CString(_T('\"')) + s + CString(_T('\"'));
}

static HRESULT StartEditApplication(const CString &path, HWND window, CProcess &process)
{
	CString command;
	Registry::ReadRegEditor(command);
	if (command.IsEmpty())
	{
#ifdef UNDER_CE
		command = _T("\\Windows\\");
#else
		if (!Common::FileSystem::GetWindowsDirectory(command))
		{
			return 0;
		}
		Common::FileSystem::FileName::NormalizeDirPathPrefix(command);
#endif
		command += _T("notepad.exe");
	}

	DWORD res = process.Create(command, GetQuotedString(path), NULL);
	if ( 0 != res)
	{
		::MessageBox(window, Lang::LangString(IDS_CANNOT_START_EDITOR, 0x03020282), _T("7-Zip"), MB_OK  | MB_ICONSTOP);
	}
	return res;
}

static HRESULT StartApplication(const CString &dir, const CString &path, HWND window, CProcess &process)
{
	UINT32 result;
	SHELLEXECUTEINFO execInfo;
	execInfo.cbSize = sizeof(execInfo);
	execInfo.fMask = SEE_MASK_NOCLOSEPROCESS
#ifndef UNDER_CE
					 | SEE_MASK_FLAG_DDEWAIT
#endif
					 ;
	execInfo.hwnd = NULL;
	execInfo.lpVerb = NULL;
	const CString sysPath = String::GetSystemString(path);
	const CString sysDir = String::GetSystemString(dir);
	execInfo.lpFile = sysPath;
	execInfo.lpParameters = NULL;
	execInfo.lpDirectory =
#ifdef UNDER_CE
		NULL
#else
		sysDir.IsEmpty() ? NULL : (LPCTSTR)sysDir
#endif
		;
	execInfo.nShow = SW_SHOWNORMAL;
	execInfo.hProcess = 0;
	::ShellExecuteEx(&execInfo);
	result = (UINT32)(UINT_PTR)execInfo.hInstApp;
	process.Attach(execInfo.hProcess);

	if (result <= 32)
	{
		switch(result)
		{
			case SE_ERR_NOASSOC:
				::MessageBox(window,
							 Error::FormatMessage(::GetLastError()),
							 // _T("There is no application associated with the given file name extension"),
							 _T("7-Zip"), MB_OK | MB_ICONSTOP);
		}
	}
	return S_OK;
}

static void StartApplicationDontWait(const CString &dir, const CString &path, HWND window)
{
	CProcess process;
	StartApplication(dir, path, window, process);
}

bool CDirectory::EditItem(int index)
{
	if (IsItemFolder(index))
		return false;
	if (!_parentFolders.empty())
	{
		OpenItemInArchive(index, false, true, true);
		return true;
	}
	CProcess process;
	StartEditApplication(GetItemFullPath(index), m_hWnd, process);
	return true;
}

void CDirectory::OpenFolderExternal(int index)
{
	CString fsPrefix = GetFsPath();
	CString name;
	if (index == kParentIndex)
	{
		int pos = fsPrefix.ReverseFind(CHAR_PATH_SEPARATOR);
		if (pos >= 0 && pos == fsPrefix.GetLength() - 1)
		{
			CString s = fsPrefix.Left(pos);
			pos = s.ReverseFind(CHAR_PATH_SEPARATOR);
			if (pos >= 0)
			{
				fsPrefix = s.Left(pos + 1);
			}
		}
		name = fsPrefix;
	}
	else
	{
		name = fsPrefix + GetItemRelPath(index) + CHAR_PATH_SEPARATOR;
	}
	StartApplicationDontWait(fsPrefix, name, m_hWnd);
}

void CDirectory::OpenItem(int index, bool tryInternal, bool tryExternal)
{
	CDisableTimerProcessing disableTimerProcessing1(*this);
	if (!_parentFolders.empty())
	{
		OpenItemInArchive(index, tryInternal, tryExternal, false);
		return;
	}
	CString name = GetItemRelPath(index);
	if (IsNameVirus(name))
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_VIRUS, 0x03020284);
		return;
	}
	CString prefix = GetFsPath();
	CString fullPath = prefix + name;
	if (tryInternal)
		if (!tryExternal || !DoItemAlwaysStart(name))
		{
			HRESULT res = OpenItemAsArchive(index);
			if (res == S_OK || res == E_ABORT)
			{
				return;
			}
			if (res != S_FALSE)
			{
				CWindows::MessageBoxError(m_hWnd, res);
				return;
			}
		}
	if (tryExternal)
	{
		// SetCurrentDirectory opens HANDLE to folder!!!
		StartApplicationDontWait(prefix, fullPath, m_hWnd);
	}
}

class CThreadCopyFrom: public CProgressThreadVirt
{
	HRESULT ProcessVirt();
public:
	CString PathPrefix;
	CString Name;

	CComPtr<IFolderOperations> FolderOperations;
	CComPtr<IProgress> UpdateCallback;
	CUpdateCallback100Imp* UpdateCallbackSpec;
	CThreadCopyFrom(CProgressThreadVirt::ThreadPoolType* pool) :
		CProgressThreadVirt(pool)
	{}
};

HRESULT CThreadCopyFrom::ProcessVirt()
{
	CStringVector fileNames;
	std::vector<const wchar_t*> fileNamePointers;
	fileNames.push_back(Name);
	fileNamePointers.push_back(fileNames[0]);
	return FolderOperations->CopyFrom(PathPrefix, &fileNamePointers.front(), fileNamePointers.size(), UpdateCallback);
}

HRESULT CDirectory::OnOpenItemChanged(const CString &folderPath, const CString &itemName,
								  bool usePassword, const CString &password)
{
	CComPtr<IFolderOperations> folderOperations;
	if (_folder.QueryInterface(&folderOperations) != S_OK)
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_OPERATION_IS_NOT_SUPPORTED, 0x03020208);
		return E_FAIL;
	}

	CThreadCopyFrom t(&m_pool);
	t.UpdateCallbackSpec = new CComObject<CUpdateCallback100Imp>;
	t.UpdateCallbackSpec->FinalConstruct();
	t.UpdateCallback = t.UpdateCallbackSpec;
	t.UpdateCallbackSpec->ProgressDialog = &t.ProgressDialog;
	t.Name = itemName;
	t.PathPrefix = folderPath;
	Common::FileSystem::FileName::NormalizeDirPathPrefix(t.PathPrefix);
	t.FolderOperations = folderOperations;
	t.UpdateCallbackSpec->Init(usePassword, password);
	HRESULT hRes(t.Create(itemName, m_hWnd));
	if(S_OK != hRes)
	{
		return hRes;
	}
	return t.Result;
}

LRESULT CDirectory::OnOpenItemChanged(LPARAM lParam)
{
	CTmpProcessInfo &tmpProcessInfo = *(CTmpProcessInfo*)lParam;
	if (tmpProcessInfo.FullPathFolderPrefix != GetCurrentDirectory())
	{
		return 0;
	}

	
	HRESULT result = OnOpenItemChanged(tmpProcessInfo.FolderPath, tmpProcessInfo.ItemName,
		tmpProcessInfo.UsePassword, tmpProcessInfo.Password);
	if (result != S_OK)
	{
		return 0;
	}
	return 1;
}


class CExitEventLauncher
{
public:
	Common::System::Threading::CEvent _exitManualEvent;
	CExitEventLauncher()
	{
		if (_exitManualEvent.Create(TRUE) != S_OK)
		{
			throw 9387173;
		}
	};
	~CExitEventLauncher()
	{
		_exitManualEvent.SetEvent();
	}
} g_ExitEventLauncher;

static void WINAPI MyThreadFunction(LPVOID param)
{
	std::unique_ptr<CTmpProcessInfo> tmpProcessInfoPtr((CTmpProcessInfo*)param);
	const CTmpProcessInfo* tmpProcessInfo = tmpProcessInfoPtr.get();

	HANDLE hProcess = tmpProcessInfo->ProcessHandle;
	HANDLE events[2] = { g_ExitEventLauncher._exitManualEvent, hProcess};
	DWORD waitResult = ::WaitForMultipleObjects(2, events, FALSE, INFINITE);
	::CloseHandle(hProcess);
	//if (waitResult == WAIT_OBJECT_0 + 0)
	//  return 0;
	if (waitResult != WAIT_OBJECT_0 + 1)
	{
		return;    // 1;
	}
	Sleep(200);
	Common::FileSystem::File::CFileInfo newFileInfo;
	if (newFileInfo.Find(tmpProcessInfo->FilePath))
	{
		if (tmpProcessInfo->WasChanged(newFileInfo))
		{
			CString message = String::PatternFormat(IDS_WANT_UPDATE_MODIFIED_FILE,
													0x03020280, tmpProcessInfo->ItemName);
			if (::MessageBox(tmpProcessInfoPtr->Window, message, _T("7-Zip"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
			{
				if (SendMessage(tmpProcessInfo->Window, kOpenItemChanged, 0, (LONG_PTR)tmpProcessInfo) != 1)
				{
					::MessageBox(tmpProcessInfoPtr->Window, String::PatternFormat(IDS_CANNOT_UPDATE_FILE,
								 0x03020281, tmpProcessInfo->FilePath), _T("7-Zip"), MB_OK | MB_ICONSTOP);
					// return 0;
					return;
				}
			}
		}
	}
	tmpProcessInfo->DeleteDirAndFile();
	return;// 0;
}

void CDirectory::OpenItemInArchive(int index, bool tryInternal, bool tryExternal, bool editMode)
{
	const CString name = GetItemName(index);
	if (IsNameVirus(name))
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_VIRUS, 0x03020284);
		return;
	}

	CComPtr<IFolderOperations> folderOperations;
	if (_folder.QueryInterface(&folderOperations) != S_OK)
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_OPERATION_IS_NOT_SUPPORTED, 0x03020208);
		return;
	}

	bool tryAsArchive = tryInternal && (!tryExternal || !DoItemAlwaysStart(name));

	CString fullVirtPath = GetCurrentDirectory() + name;

	Common::FileSystem::CTempDirectory tempDirectory;
	tempDirectory.Create(kTempDirPrefix);
	CString tempDir = tempDirectory.GetPath();
	CString tempDirNorm = tempDir;
	Common::FileSystem::FileName::NormalizeDirPathPrefix(tempDirNorm);

	CString tempFilePath = tempDirNorm + Common::FileSystem::GetCorrectFsPath(name);

	CTempFileInfo tempFileInfo;
	tempFileInfo.ItemName = name;
	tempFileInfo.FolderPath = tempDir;
	tempFileInfo.FilePath = tempFilePath;
	tempFileInfo.NeedDelete = true;

	if (tryAsArchive)
	{
		CComPtr<IInArchiveGetStream> getStream;
		_folder.QueryInterface(&getStream);
		if (getStream)
		{
			CComPtr<ISequentialInStream> subSeqStream;
			getStream->GetStream(index, &subSeqStream);
			if (subSeqStream)
			{
				CComPtr<IInStream> subStream;
				subSeqStream.QueryInterface(&subStream);
				if (subStream)
				{
					bool encrypted;
					if (OpenItemAsArchive(subStream, tempFileInfo, fullVirtPath, CString(), encrypted) == S_OK)
					{
						tempDirectory.DisableDeleting();
						//RefreshListCtrl();
						return;
					}
				}
			}
		}
	}


	std::vector<UINT32> indices;
	indices.push_back(index);

	CStringVector messages;

	bool usePassword = false;
	CString password;
	if (_parentFolders.size() > 0)
	{
		const auto &fl = _parentFolders.back();
		usePassword = fl->UsePassword;
		password = fl->Password;
	}

	HRESULT result = CopyTo(indices, tempDirNorm, false, true, &messages, usePassword, password);

	if (_parentFolders.size() > 0)
	{
		auto &fl = _parentFolders.back();
		fl->UsePassword = usePassword;
		fl->Password = password;
	}

	if (!messages.empty())
	{
		return;
	}
	if (result != S_OK)
	{
		if (result != E_ABORT)
		{
			CWindows::MessageBoxError(m_hWnd, result);
		}
		return;
	}


	if (tryAsArchive)
	{
		bool encrypted;
		if (OpenItemAsArchive(NULL, tempFileInfo, fullVirtPath, CString(), encrypted) == S_OK)
		{
			tempDirectory.DisableDeleting();
			//RefreshListCtrl();
			return;
		}
	}

	std::unique_ptr<CTmpProcessInfo> tmpProcessInfoPtr(new CTmpProcessInfo());
	CTmpProcessInfo* tmpProcessInfo = tmpProcessInfoPtr.get();
	tmpProcessInfo->FolderPath = tempDir;
	tmpProcessInfo->FilePath = tempFilePath;
	tmpProcessInfo->NeedDelete = true;
	tmpProcessInfo->UsePassword = usePassword;
	tmpProcessInfo->Password = password;

	if (!tmpProcessInfo->FileInfo.Find(tempFilePath))
	{
		return;
	}

	CTmpProcessInfoRelease tmpProcessInfoRelease(*tmpProcessInfo);

	if (!tryExternal)
	{
		return;
	}

	CProcess process;
	HRESULT res;
	if (editMode)
	{
		res = StartEditApplication(tempFilePath, m_hWnd, process);
	}
	else
	{
		res = StartApplication(tempDirNorm, tempFilePath, m_hWnd, process);
	}

	if ((HANDLE)process == 0)
	{
		return;
	}

	tmpProcessInfo->Window = m_hWnd;
	tmpProcessInfo->FullPathFolderPrefix = GetCurrentDirectory();
	tmpProcessInfo->ItemName = name;
	tmpProcessInfo->ProcessHandle = process.Detach();

	m_pool->AddTask(MyThreadFunction, tmpProcessInfo);
	tempDirectory.DisableDeleting();
	tmpProcessInfoPtr.release();
	tmpProcessInfoRelease._needDelete = false;
}

} //namespace FileSystem
} //namespace FileManager
